// Effective C++ 条款31：将文件间的编译依存关系降至最低
// 编译依存问题的本质
// 在C++中，当一个文件包含另一个文件的头文件时，就建立了编译依存关系。这会导致以下问题：
// 1.重新编译链的扩大：修改一个头文件可能导致大量依赖它的文件需要重新编译
// 2.编译时间增加：大型项目中，不必要的依赖会显著增加编译时间
// 3.实现细节泄露：头文件中包含的实现细节会暴露给所有包含该头文件的代码

// 传统实现方式的问题
// 考虑一个传统的类定义：
// Person.h
#include <string>
#include "Date.h"
#include "Address.h"

class Person
{
public:
    Person(const std::string &name, const Date &birthday,
           const Address &addr);
    std::string getName() const;
    Date getBirthday() const;
    Address getAddress() const;

private:
    std::string name;
    Date birthday;
    Address address;
};
// 这种实现存在的问题：
// 任何使用Person的文件都必须包含string、Date和Address的头文件
// 如果这些头文件中任何一个改变，所有包含Person.h的文件都需要重新编译
// 即使只是Date或Address的实现细节改变，也会触发重新编译

// 解决方案：接口与实现分离
// 1. 使用前置声明（Forward Declaration）
// Person.h
#include <string> // 标准库类型通常需要包含

class Date;    // 前置声明
class Address; // 前置声明

class Person
{
public:
    Person(const std::string &name);
    ~Person();

    void setBirthday(const Date &date);
    void setAddress(const Address &addr);

    // 注意：这些方法在头文件中只声明，不定义
    std::string getName() const;
    // 返回类型不能是Date，因为只有前置声明
    std::string getBirthdayString() const;
    std::string getAddressString() const;

private:
    std::string name;
    Date *pBirthday;   // 使用指针，只需前置声明
    Address *pAddress; // 使用指针，只需前置声明
};
// Person.cpp
#include "Person.h"
#include "Date.h"    // 这里需要完整定义
#include "Address.h" // 这里需要完整定义

Person::Person(const std::string &name)
    : name(name), pBirthday(nullptr), pAddress(nullptr) {}

Person::~Person()
{
    delete pBirthday;
    delete pAddress;
}

void Person::setBirthday(const Date &date)
{
    if (pBirthday)
        delete pBirthday;
    pBirthday = new Date(date); // 需要Date的完整定义
}

// 其他方法实现...

// 这种方法的问题：
// 仍然暴露了实现细节（使用指针）
// 需要管理动态内存（分配和释放）

// 2. 指向实现的指针（PIMPL惯用法）
// Person.h
#include <string>
#include <memory> // 用于std::unique_ptr

class Person
{
public:
    Person(const std::string &name, const std::string &birthdate,
           const std::string &address);
    ~Person(); // 需要显式定义析构函数

    std::string getName() const;
    std::string getBirthday() const;
    std::string getAddress() const;

private:
    class PersonImpl;                  // 前置声明私有实现类
    std::unique_ptr<PersonImpl> pImpl; // 指向实现的指针
};
// Person.cpp
#include "Person.h"
#include "Date.h"
#include "Address.h"

class Person::PersonImpl
{
public:
    PersonImpl(const std::string &name, const std::string &birthdate,
               const std::string &address)
        : name(name),
          birthday(birthdate), // 假设Date有从string构造的能力
          address(address)     // 假设Address有从string构造的能力
    {
    }

    std::string name;
    Date birthday;
    Address address;
};

Person::Person(const std::string &name, const std::string &birthdate,
               const std::string &address)
    : pImpl(std::make_unique<PersonImpl>(name, birthdate, address))
{
}

Person::~Person() = default; // 必须在这里定义，因为PersonImpl是完整类型

std::string Person::getName() const
{
    return pImpl->name;
}

std::string Person::getBirthday() const
{
    return pImpl->birthday.toString(); // 假设Date有toString方法
}

std::string Person::getAddress() const
{
    return pImpl->address.toString(); // 假设Address有toString方法
}

// 3. 使用抽象基类（Handle/Body 惯用法）
// PersonInterface.h
#include <string>

class PersonInterface
{
public:
    virtual ~PersonInterface() {}
    virtual std::string getName() const = 0;
    virtual std::string getBirthday() const = 0;
    virtual std::string getAddress() const = 0;

    // 工厂函数
    static std::unique_ptr<PersonInterface> create(
        const std::string &name,
        const std::string &birthdate,
        const std::string &address);
};

// Person.cpp
#include "PersonInterface.h"
#include "Date.h"
#include "Address.h"

class PersonImpl : public PersonInterface
{
public:
    PersonImpl(const std::string &name, const std::string &birthdate,
               const std::string &address)
        : name(name),
          birthday(birthdate),
          address(address)
    {
    }

    std::string getName() const override
    {
        return name;
    }

    std::string getBirthday() const override
    {
        return birthday.toString();
    }

    std::string getAddress() const override
    {
        return address.toString();
    }

private:
    std::string name;
    Date birthday;
    Address address;
};

std::unique_ptr<PersonInterface> PersonInterface::create(
    const std::string &name,
    const std::string &birthdate,
    const std::string &address)
{
    return std::make_unique<PersonImpl>(name, birthdate, address);
}

// 这些技术的优势
// 1.降低编译依存关系：
//      客户代码只依赖接口，不依赖实现细节
//      实现细节的改变不会导致客户代码重新编译
// 2.加快编译速度：
//      减少头文件包含
//      减少级联重新编译
// 3.提高封装性：
//      实现细节完全隐藏
//      接口与实现彻底分离

// 实际应用中的考虑因素
// 1. 性能影响
// 使用PIMPL或抽象基类会带来一些性能开销：
//      额外的间接寻址
//      动态内存分配
//      虚函数调用开销（对于抽象基类方法）
// 2. 接口设计
//      接口应该尽可能稳定，以减少客户代码的重新编译需求：
//      提供完整但最小化的接口
//      避免在接口中暴露实现细节
//      考虑向前兼容性
// 3. 适用场景
//      这些技术特别适用于：
//      频繁变化的实现
//      大型项目中的核心组件
//      需要提供稳定ABI的库

// 实际案例分析
// Qt框架的做法
// Qt是一个大型C++框架，它广泛使用PIMPL模式（称为d指针），但也有一些技巧来平衡可读性：

// 命名约定：实现类通常命名为ClassNamePrivate
// 文档：详细记录公共API，减少对实现细节的依赖
// 工具支持：提供工具来简化PIMPL模式的使用
// 大型游戏引擎的做法
// 许多游戏引擎在核心系统中使用PIMPL，但在性能关键路径上避免使用：

// 接口层：引擎API使用PIMPL
// 核心系统：选择性使用PIMPL
// 性能关键代码：通常避免使用PIMPL，直接暴露实现以优化性能
// 建议的决策流程
// 针对您的情况，可以考虑以下决策流程：

// 问自己：这个类是否是公共API的一部分？
// 如果是，优先考虑使用PIMPL
// 问自己：这个类的实现是否经常变化？
// 如果是，考虑使用PIMPL
// 问自己：这个类是否在性能关键路径上？
// 如果是，可能需要避免PIMPL或使用部分PIMPL
// 问自己：这个类的客户代码有多少？
// 如果有很多客户代码，使用PIMPL可以减少重新编译
// 问自己：团队对PIMPL模式的熟悉程度如何？
// 如果团队不熟悉，可能需要更多文档和指导
// 总结
// 在大型项目中，PIMPL模式是一个有价值的工具，但需要权衡使用。不必对所有类都使用PIMPL，而是应该根据类的角色、变化频率和性能需求来选择。

// 通过选择性使用PIMPL、良好的文档和工具支持，可以在降低编译依赖和保持代码可读性之间取得平衡。记住，软件设计总是关于权衡，没有放之四海而皆准的解决方案。

// 根据您之前提到的C++参数传递记忆，在设计API时，还应该考虑如何高效地传递参数，特别是对于包含大型自定义类的接口。